﻿using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;

namespace Uportal.Shp.Main.ListFieldIterators
{
    public class BaseListFieldIterator : Microsoft.SharePoint.WebControls.ListFieldIterator
    {
        // create dynamic setter methods that wrap the internal
        // ControlMode and FieldName properties of the TemplateContainer class       
        protected static GenericSetter set_TemplateContainer_ControlMode =
                ILUtils.CreateSetMethod(typeof(TemplateContainer), "ControlMode");
        protected static GenericSetter set_TemplateContainer_FieldName =
            ILUtils.CreateSetMethod(typeof(TemplateContainer), "FieldName");

        protected List<FieldDisplayRuleItem> _dynamicRules = new List<FieldDisplayRuleItem>();

        // get references for the frequently used objects
        protected SPSite _site = SPContext.Current.Site;
        protected SPWeb _web = SPContext.Current.Web;
        protected SPContext _context = SPContext.Current;
        protected SPFormContext _formContext = SPContext.Current.FormContext;

        protected override void CreateChildControls()
        {
            this.Controls.Clear();
            if (this.ControlTemplate == null)
            {
                throw new ArgumentException("Could not find ListFieldIterator control template.");
            }

            for (int i = 0; i < base.Fields.Count; i++)
            {
                SPField field = base.Fields[i];
                String fieldName = field.InternalName;

                // check if the current field is on the list of "hidden" fields in the current display mode
                // or whether there is a "global" rule to hide fields
                FieldDisplayRuleItem exception = _dynamicRules.FirstOrDefault(
                    // empty (null) value means there is no restriction for the control mode
                    e => ((e.ControlModes == null) || (e.ControlModes.Contains(ControlMode))) &&
                        // empty (null) value means there is no restriction for the field name
                    ((e.FieldNames == null) || (e.FieldNames.Contains(fieldName))) &&
                    (e.Rule == FieldDisplayRule.Hidden));

                if ((!this.IsFieldExcluded(field)) && (exception == null))
                {
                    TemplateContainer child = new TemplateContainer();
                    this.Controls.Add(child);
                    SPControlMode controlMode = GetControlMode(fieldName);
                    // use the dynamic setter to access internal properties
                    set_TemplateContainer_ControlMode(child, controlMode);
                    set_TemplateContainer_FieldName(child, fieldName);
                    this.ControlTemplate.InstantiateIn(child);
                }
            }
        }

        private SPControlMode GetControlMode(string fieldName)
        {

            FieldDisplayRuleItem rule = _dynamicRules.FirstOrDefault(
                e => ((e.ControlModes == null) || (e.ControlModes.Contains(ControlMode))) &&
                ((e.FieldNames == null) || (e.FieldNames.Contains(fieldName))) &&
                (e.Rule == FieldDisplayRule.Display));

            SPControlMode result = (rule == null) ? ControlMode : SPControlMode.Display;

            return result;
        }

    }

    public class FieldDisplayRuleItem
    {
        // list of field names the rule applies to
        public List<String> FieldNames { get; set; }
        // list of control modes the rule applies to
        public List<SPControlMode> ControlModes { get; set; }
        // the resulting display exeption rule
        public FieldDisplayRule Rule { get; set; }
    }

    public enum FieldDisplayRule
    {
        Hidden,
        Display
    }

    public delegate void GenericSetter(object target, object value);
    public delegate object GenericGetter(object target);

    class ILUtils
    {
        ///
        /// Creates a dynamic setter for the property
        ///
        public static GenericSetter CreateSetMethod(Type targetType, String propName)
        {

            GenericSetter result = null;

            PropertyInfo propertyInfo = targetType.GetProperty(propName,
                BindingFlags.NonPublic | BindingFlags.Instance);

            if (propertyInfo != null)
            {
                MethodInfo setMethod = propertyInfo.GetSetMethod(true);
                if (setMethod != null)
                {

                    Type[] arguments = new Type[2];
                    arguments[0] = arguments[1] = typeof(object);

                    DynamicMethod setter = new DynamicMethod(
                      String.Concat("_Set", propertyInfo.Name, "_"),
                      typeof(void), arguments, propertyInfo.DeclaringType);
                    ILGenerator generator = setter.GetILGenerator();
                    generator.Emit(OpCodes.Ldarg_0);
                    generator.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
                    generator.Emit(OpCodes.Ldarg_1);

                    if (propertyInfo.PropertyType.IsClass)
                        generator.Emit(OpCodes.Castclass, propertyInfo.PropertyType);
                    else
                        generator.Emit(OpCodes.Unbox_Any, propertyInfo.PropertyType);

                    generator.EmitCall(OpCodes.Callvirt, setMethod, null);
                    generator.Emit(OpCodes.Ret);

                    result = (GenericSetter)setter.CreateDelegate(typeof(GenericSetter));
                }
            }
            return result;
        }

        ///
        /// Creates a dynamic getter for the property
        ///
        public static GenericGetter CreateGetMethod(Type targetType, String propName)
        {

            GenericGetter result = null;

            PropertyInfo propertyInfo = targetType.GetProperty(propName,
                BindingFlags.NonPublic | BindingFlags.Instance);

            if (propertyInfo != null)
            {
                MethodInfo getMethod = propertyInfo.GetGetMethod(true);
                if (getMethod != null)
                {

                    Type[] arguments = new Type[1];
                    arguments[0] = typeof(object);

                    DynamicMethod getter = new DynamicMethod(
                      String.Concat("_Get", propertyInfo.Name, "_"),
                      typeof(object), arguments, propertyInfo.DeclaringType);
                    ILGenerator generator = getter.GetILGenerator();
                    generator.DeclareLocal(typeof(object));
                    generator.Emit(OpCodes.Ldarg_0);
                    generator.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
                    generator.EmitCall(OpCodes.Callvirt, getMethod, null);

                    if (!propertyInfo.PropertyType.IsClass)
                        generator.Emit(OpCodes.Box, propertyInfo.PropertyType);

                    generator.Emit(OpCodes.Ret);

                    result = (GenericGetter)getter.CreateDelegate(typeof(GenericGetter));
                }
            }

            return result;

        }

    }
}
